package com.zhg.shortlink.service;

import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import java.lang.management.ManagementFactory;
import java.net.InetAddress;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 * Redis时间分片布隆过滤器服务
 * @author 朱洪刚
 * @version 1.0
 * @data 2025/10/22 14:16
 */
@Slf4j
@RequiredArgsConstructor
@Service
public class RedisTimeBasedBloomFilterService {

    private final RedissonClient redissonClient;

    @Value("${shortlink.bloom.time-slice.hours:6}")
    private int timeSliceHours;

    @Value("${shortlink.bloom.time-slice.redis-keep-count:32}")
    private int redisKeepSliceCount; // Redis保留的时间片数量（8天=6*32）

    // Redis时间分片布隆过滤器映射
    private final ConcurrentMap<String, RBloomFilter<String>> redisTimeSlices = new ConcurrentHashMap<>();

    private volatile String currentTimeSlice;

    // 每个时间片配置
    private static final long EXPECTED_INSERTIONS = 216_000_000L; // 6小时预期容量
    private static final double FALSE_PROBABILITY = 0.01;

    // 节点ID缓存
    private volatile String nodeId;

    @PostConstruct
    public void init() {
        currentTimeSlice = getCurrentTimeSliceKey();
        createRedisTimeSlice(currentTimeSlice);
        log.info("Redis时间分片布隆过滤器初始化完成，当前时间片: {}", currentTimeSlice);
    }

    public boolean mightContain(String shortCode) {
        // 检查所有活跃的Redis时间分片
        for (RBloomFilter<String> slice : redisTimeSlices.values()) {
            try {
                if (slice.contains(shortCode)) {
                    return true;
                }
            } catch (Exception e) {
                log.warn("检查Redis时间分片失败: {}", e.getMessage());
            }
        }
        return false;
    }

    public void add(String shortCode) {
        String currentSlice = getCurrentTimeSliceKey();

        // 如果时间片发生变化，创建新的时间片
        if (!currentSlice.equals(currentTimeSlice)) {
            synchronized (this) {
                if (!currentSlice.equals(currentTimeSlice)) {
                    createRedisTimeSlice(currentSlice);
                    currentTimeSlice = currentSlice;
                    log.info("创建新的Redis时间片: {}", currentSlice);
                }
            }
        }

        // 添加到当前Redis时间片
        RBloomFilter<String> slice = redisTimeSlices.get(currentTimeSlice);
        if (slice != null) {
            try {
                slice.add(shortCode);
            } catch (Exception e) {
                log.error("添加到Redis时间分片失败: slice={}, shortCode={}", currentTimeSlice, shortCode, e);
            }
        }
    }

    /**
     * 定时清理过期的Redis时间片
     */
    /**
     * 简化的清理方法：所有节点都执行，无需分布式锁
     */
    @Scheduled(fixedRate = 300000) // 每5分钟检查一次
    public void cleanupExpiredSlices() {
        try {
            log.debug("开始清理过期时间片 - 节点: {}", getNodeId());
            doCleanupExpiredSlices();
        } catch (Exception e) {
            log.error("清理过期时间片失败 - 节点: {}", getNodeId(), e);
        }
    }

    /**
     * 实际的清理逻辑（支持多节点并发执行）
     */
    private void doCleanupExpiredSlices() {
        try {
            List<String> expiredSlices = new ArrayList<>();

            // 1. 找出过期的时间片
            for (String sliceKey : redisTimeSlices.keySet()) {
                if (isSliceExpired(sliceKey)) {
                    expiredSlices.add(sliceKey);
                }
            }

            int memoryCleanedCount = 0;
            int redisCleanedCount = 0;

            // 2. 清理过期时间片
            for (String expiredSlice : expiredSlices) {
                // 2.1 清理内存映射（每个节点清理自己的）
                RBloomFilter<String> removed = redisTimeSlices.remove(expiredSlice);
                if (removed != null) {
                    memoryCleanedCount++;

                    try {
                        // 2.2 清理Redis数据（多节点并发执行，幂等安全）
                        if (removed.isExists()) {
                            removed.delete();
                            redisCleanedCount++;
                            log.info("✅ 清理时间片成功: {} - 节点: {}", expiredSlice, getNodeId());
                        } else {
                            log.debug("时间片已被清理: {} - 节点: {}", expiredSlice, getNodeId());
                        }
                    } catch (Exception e) {
                        log.warn("Redis清理失败: {} - 节点: {}, 错误: {}",
                                expiredSlice, getNodeId(), e.getMessage());
                        // 注意：即使Redis删除失败，内存已经清理，不会影响应用运行
                    }
                }
            }

            if (memoryCleanedCount > 0 || redisCleanedCount > 0) {
                log.info("🧹 清理完成 - 节点: {}, 内存清理: {}, Redis清理: {}, 剩余片数: {}",
                        getNodeId(), memoryCleanedCount, redisCleanedCount, redisTimeSlices.size());
            }

        } catch (Exception e) {
            log.error("清理过期时间片失败 - 节点: {}", getNodeId(), e);
        }
    }

    /**
     * 获取当前节点ID
     * 格式: hostname-pid-timestamp
     */
    private String getNodeId() {
        if (nodeId == null) {
            synchronized (this) {
                if (nodeId == null) {
                    try {
                        String hostname = InetAddress.getLocalHost().getHostName();
                        String pid = ManagementFactory.getRuntimeMXBean().getName().split("@")[0];
                        long timestamp = System.currentTimeMillis() % 100000; // 取后5位避免太长
                        nodeId = String.format("%s-%s-%d", hostname, pid, timestamp);
                    } catch (Exception e) {
                        // 如果获取失败，使用备用方案
                        nodeId = "node-" + System.currentTimeMillis() % 10000;
                        log.warn("获取节点信息失败，使用备用节点ID: {}", nodeId);
                    }
                    log.info("节点ID初始化: {}", nodeId);
                }
            }
        }
        return nodeId;
    }

    private String getCurrentTimeSliceKey() {
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime sliceTime = now.withMinute(0).withSecond(0).withNano(0)
                .withHour((now.getHour() / timeSliceHours) * timeSliceHours);
        return "redis_bloom_" + sliceTime.format(DateTimeFormatter.ofPattern("yyyyMMdd_HH"));
    }

    private void createRedisTimeSlice(String sliceKey) {
        try {
            RBloomFilter<String> slice = redissonClient.getBloomFilter(sliceKey);
            if (!slice.isExists()) {
                slice.tryInit(EXPECTED_INSERTIONS, FALSE_PROBABILITY);
            }
            redisTimeSlices.put(sliceKey, slice);
            log.info("创建Redis时间分片: {}", sliceKey);
        } catch (Exception e) {
            log.error("创建Redis时间分片失败: {}", sliceKey, e);
        }
    }

    private boolean isSliceExpired(String sliceKey) {
        try {
            String timeStr = sliceKey.replace("redis_bloom_", "");
            LocalDateTime sliceTime = LocalDateTime.parse(timeStr,
                    DateTimeFormatter.ofPattern("yyyyMMdd_HH"));
            LocalDateTime expireTime = sliceTime.plusHours(timeSliceHours * redisKeepSliceCount);
            return LocalDateTime.now().isAfter(expireTime);
        } catch (Exception e) {
            log.warn("解析Redis时间片key失败: {}", sliceKey);
            return true;
        }
    }

    public String getStats() {
        return String.format("Redis时间分片统计 - 活跃片数: %d, 当前片: %s",
                redisTimeSlices.size(), currentTimeSlice);
    }
}
